/*
 * This file is part of Catfish.
 * Copyright (C) 2010 Namazu Studios LLC
 * 
 * Catfish is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of 
 * the License, or (at your option) any later version.
 * 
 * Catfish is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with Catfish.  If not, please visit:
 *  
 * http://www.gnu.org/licenses/
 *  
 */
package com.namazustudios.catfish.utility;

import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.inject.Inject;
import com.namazustudios.catfish.Catfish;
import com.namazustudios.catfish.annotation.Delete;
import com.namazustudios.catfish.annotation.PostPut;

public abstract class AbstractDataObject {

	@Inject
	transient private Catfish catfish;

	@Inject
	transient private MemcacheService memcache;

	@Inject(optional = true)
	private Logger logger = Logger
			.getLogger(AbstractDataObject.class.getName());

	protected final Catfish getCatfish() {

		if (catfish == null) {
			throw new IllegalStateException("Object " + this
					+ " not bound to instance of Catfish.");
		}

		return catfish;
	}

	/**
	 * Puts this object into the datastore.
	 */
	public final void put() {
		getCatfish().put(this);
	}

	/**
	 * Puts this object into the datastore using the given transaction.
	 * 
	 * @param transaction
	 */
	public final void put(Transaction transaction) {
		getCatfish().put(transaction, this);
	}

	/**
	 * Gets this object from the datastore. If the object was already fetched,
	 * it fetches the fields again.
	 * 
	 * @throws EntityNotFoundException
	 *             if the entity could not be found in the datastore
	 */
	public final void delete() {
		getCatfish().delete(this);
	}

	/**
	 * Deletes this object from the datastore using the given transaction.
	 * 
	 * @param transaction
	 *            the transaction
	 */
	public final void delete(Transaction transaction) {
		getCatfish().delete(transaction, this);
	}

	/**
	 * Gets the object's data from the datastore.
	 */
	public final void get() throws EntityNotFoundException {
		final Catfish catfish = getCatfish();
		final Key key = catfish.getKey(this);
		final Entity entity = catfish.getDataSource().get(key);

		catfish.unmarshaller().unmarshall(this, entity);

	}

	/**
	 * Changes this object's key such that it mathces the given key and then
	 * umarshalls the fetched entity into this object.
	 * 
	 * @param key
	 *            the key
	 * @throws EntityNotFoundException
	 */
	public final void get(final Key key) throws EntityNotFoundException {
		final Catfish catfish = getCatfish();
		final Entity entity = catfish.getDataSource().get(key);

		catfish.setKey(this, key);
		catfish.unmarshaller().unmarshall(this, entity);

	}

	/**
	 * Gets the data from the data store matching this object's key and 
	 * unmarshalls it into this object. 
	 * 
	 * @param transaction
	 * @throws EntityNotFoundException
	 */
	public final void get(Transaction transaction) throws EntityNotFoundException {
		final Catfish catfish = getCatfish();
		final Key key = catfish.getKey(this);
		final Entity entity = catfish.getDataSource().get(transaction, key);

		catfish.unmarshaller().unmarshall(this, entity);

	}

	/**
	 * Sets this object's key to the given key, fetches the object from
	 * the datastore and then unmarshalls it into this object.
	 * 
	 * @param transaction the transaction
	 * @param key the key
	 * 
	 * @throws EntityNotFoundException
	 */
	public final void get(Transaction transaction, Key key)
			throws EntityNotFoundException {
		final Catfish catfish = getCatfish();
		final Entity entity = catfish.getDataSource().get(key);

		catfish.setKey(this, key);
		catfish.unmarshaller().unmarshall(this, transaction, entity);

	}

	/**
	 * Invalidates the parent object from the memory cache.
	 */
	@Delete
	@PostPut
	@SuppressWarnings("unused")
	private final void invalidateParent() {
		Key key;
		Set<Object> invalid = new HashSet<Object>();

		if (!getCatfish().isCacheable(this)) {
			return;
		}

		key = getCatfish().getKey(this);

		if (key == null) {
			return;
		}

		while ((key = key.getParent()) != null) {
			invalid.add(key);
			logger.finer("Expiring memcached object: " + key);
		}

		memcache.deleteAll(invalid);

	}
}
